#!/usr/bin/env python
# coding=utf8
# Last Change: 2010-05-10 10:49:51

from database import Account, Message, AccessPoint
import gtalk

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
from google.appengine.api import xmpp
from google.appengine.api.xmpp import InvalidMessageError

from datetime import datetime
import logging
import re

class XMPPHandler(webapp.RequestHandler):

  def post(self):
    fails = 0
    success = 0
    while success < 1:
      try:
        self.do_post()
        success += 1
      except:
        fails += 1
        if fails >= 5:
          raise

  def do_post(self):
    try:
      message = xmpp.Message(self.request.POST)
    except:
      return 
    user_gtalk, user_agent = extract_sender(message.sender)
    m = re.search('^([^/]+)', message.to)
    ap = m.group(0).lower()
    q = db.GqlQuery('SELECT * FROM Account WHERE gtalk = :1', user_gtalk)
    if (q.count(1) == 0):
      message.reply('You (%s) are not authcatied to use this gtalk group now. Please contact a@yegong.net to get more details.' % user_gtalk)
      return
    user = q.get()
    body = message.body
    if body == ':help':
      # help
      str = '\n'
      str += '** available command:\n'
      str += ':focus\n'
      str += ':focus none\n'
      str += ':focus {tag}[ @{user}][ @{user}]...\n'
      str += ':hello\n'
      str += ':help\n'
      str += ':online\n'
      str += ':list\n'
      str += ':quote {quoted_text}\n'
      message.reply(str)
    elif body == ':hello':
      # hello
      message.reply('\n** hello %s' % user.name)
    elif body == ':online':
      # online
      str = '\n'
      str += '** list all online users:\n'
      for a in Account.all():
        if xmpp.get_presence(a.gtalk):
          str += '%s ' % a.name
      message.reply(str)
    elif body == ':list':
      # list
      str = '\n'
      str += '** list all users:\n'
      for a in Account.all():
        is_online = xmpp.get_presence(a.gtalk)
        status = 'online' if is_online else 'offline'
        line = '%s  %s %s\n' % (a.name, a.gtalk, status)
        str += line
      message.reply(str)
    elif body.startswith(':focus '):
      # focus clear
      arg = body.split(' ', 1)[1].strip()
      q = db.GqlQuery('SELECT * FROM AccessPoint WHERE user = :1 AND ap = :2', user, ap)
      if arg == 'none':
        if q.count(1) == 1:
          access_point = q.get()
          access_point.delete()
        str = '\n** now the focus setting is cleared.'
        message.reply(str)
      # setup focus
      else:
        tag = None
        to = list()
        for a in arg.split(' '):
          if a.startswith('@') or a.startswith(u'＠'):
            a = a[1:].lower()
            a = get_user_by_abbr(a)
            if a:
              to.append(a)
          elif len(a) != 0:
            if a.lower() == 'none':
              a = None
            if tag:
              str = '\n** you can give one tag only when you using the {focus} command.'
              message.reply(str)
              return
            tag = a
        if q.count(1) == 0:
          access_point = AccessPoint(user=user, ap=ap, tag=tag, to=to) 
        else:
          access_point = q.get()
          access_point.tag = tag
          access_point.to = to
        db.put(access_point)
        if len(to) != 0:
          to_str = reduce(lambda x,y: x+', '+y, map(lambda x: '@' +x, to))
          str = '\n** you are focus #%s, %s via ap: %s.' % (access_point.tag, to_str, ap)
        else:
          str = '\n** you are focus #%s via ap: %s.' % (access_point.tag, ap)
        message.reply(str)
    elif body == ':focus':
      # focus query
      q = db.GqlQuery('SELECT * FROM AccessPoint WHERE user = :1 AND ap = :2', user, ap)
      if q.count(1) == 0:
        str = "\n** you didn't focus anything via ap: %s." % (ap)
      else:
        access_point = q.get()
        if access_point and len(access_point.to) != 0:
          to_str = reduce(lambda x,y: x+', '+y, map(lambda x: '@' +x, access_point.to))
          str = '\n** you are focus #%s, %s via ap: %s.' % (access_point.tag, to_str, ap)
        else:
          str = '\n** you are focus #%s via ap: %s.' % (access_point.tag, ap)
      message.reply(str)
    elif body.startswith(':quote\n'):
      # quoted text
      str = body.split('\n', 1)[1]
      if len(str) == 0:
        message.reply('\n** you cannot send a empty message')
        return
      message = Message(sender=user.name, to=[], tag=None, content=str, time=datetime.now())
      db.put(message)
      gtalk.send_message_to_all(message, user.name)
    elif body.startswith(':quote '):
      # quoted text
      str = body.split(' ', 1)[1]
      if len(str) == 0:
        message.reply('\n** you cannot send a empty message')
        return
      message = Message(sender=user.name, to=[], tag=None, content=str, time=datetime.now())
      db.put(message)
      gtalk.send_message_to_all(message, user.name)
    elif body.startswith(':'):
      # unknown command
      message.reply('\n** {%s} is unknown command' % body)
      logging.warn('{unknown command} {%s} {%s}' % (user.name, body.__repr__() ))
    else:
      # normal message, forward to everyone
      body, to = extract_to(body)
      body, tag = extract_tag(body)
      if len(body) == 0:
        message.reply('\n** you cannot send a empty message')
        return
      if not tag:
        q = db.GqlQuery('SELECT * FROM AccessPoint WHERE user = :1 AND ap = :2', user, ap)
        if q.count(1) == 1:
          tag = q.get().tag
      if len(to) == 0:
        q = db.GqlQuery('SELECT * FROM AccessPoint WHERE user = :1 AND ap = :2', user, ap)
        if q.count(1) == 1:
          access_point = q.get()
          to = access_point.to if access_point.to else to

      message = Message(sender=user.name, to=to, tag=tag, content=body, time=datetime.now())
      db.put(message)
      gtalk.send_message_to_all(message, user.name)

def get_user_by_abbr(abbr):
  if len(abbr) == 0:
    return None
  q = db.GqlQuery('SELECT * FROM Account WHERE name = :1', abbr)
  if q.count(1) == 1:
    return abbr
  else:
    for a in Account.all():
      if a.name.startswith(abbr):
        return a.name
  return None

def extract_to(content):
  to = list()
  body = ''
  pattern = re.compile(u'^\s*[@＠]([a-zA-Z]+)\s*')
  for line in content.split('\n'):
    line = line.strip()
    if len(line) == 0:
      # append the empty line if it exists
      body += line + '\n'
    else:
      m = re.search(pattern, line)
      while m:
        # remove the matched @{user}
        line = re.sub(pattern, '', line, 1)
        t = m.group(1).lower()
        t = get_user_by_abbr(t)
        if t:
          to.append(t)
        m = re.search(pattern, line)
      # remove the 'empty line' if it's for @{user} only
      if (len(line) != 0):
        body += line + '\n'
  return body.strip('\n'), to

def extract_tag(content):
  tag = None
  body = ''
  pattern = re.compile(u'\s*[#＃]([_a-zA-Z0-9]+)\s*$')
  for line in content.split('\n'):
    line = line.strip()
    if len(line) == 0:
      # append the empty line if it exists
      body += line + '\n'
    else:
      # only one tag is allowed
      m = re.search(pattern, line)
      if m and not tag:
        tag = m.group(1).lower()
        line = re.sub(pattern, '', line, 1)
      # remove the 'empty line' if it's for #{tag} only
      if (len(line) != 0):
        body += line + '\n'
  return body.strip('\n'), tag

def extract_sender(sender):
  gtalk_and_agent = sender.split('/', 2)
  user_gtalk = gtalk_and_agent[0].lower()
  if len(gtalk_and_agent) == 2:
    str = gtalk_and_agent[1].lower()
    user_agent_list = ['telepath', 'android', 'yoono', 'talk', 'gmail', 'igoogle', 'adium']
    for u in user_agent_list:
      if str.startswith(u):
        user_agent = u
        break
    else:
      user_agent = None
    if not user_agent:
      logging.debug('{unknown user agent} {%s}' % str)
  return user_gtalk, user_agent

application = webapp.WSGIApplication([('/_ah/xmpp/message/chat/', XMPPHandler)], debug=True)

def main():
  run_wsgi_app(application)

if __name__ == '__main__':
  main()
